﻿// Original author contact info: Owen Emlen (owene_1998@yahoo.com)
// Note: other individuals may also have contributed to this code
// Project hosted on CodePlex.com as of 1/10/2009 at http://www.codeplex.com/EmlenMud
using System;
using System.Runtime.Serialization;
using System.Threading;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BrainTechLLC.ThreadSafeObjects;
using BrainTechLLC;

namespace BrainTechLLC
{
    [Serializable]
    [DataContract]
    public class NotificationPickup
    {
        [DataMember]
        public List<INotificationMessage> Notifications { get; set; }
    }

    [Serializable]
    [DataContract]
    public class ConnectionPoints
    {
        public ThreadSafeLookup<int, ConnectionPoint> ConnectionPointLookupByUniqueId = new ThreadSafeLookup<int, ConnectionPoint>();
        private static object _lockObject = new object();

        private static ConnectionPoints _instance;

        public void Initialize()
        {
            UsernamePassword.WantSetCurrentlyPlaying += new EventHandler<ObjectEventArgsNonRef<int, long>>(UsernamePassword_WantSetCurrentlyPlaying);
        }

        void UsernamePassword_WantSetCurrentlyPlaying(object sender, ObjectEventArgsNonRef<int, long> e)
        {
            SetConnectionPoint(e.obj1, e.obj2);
        }

        public static ConnectionPoints GetInstance()
        {
            if (_instance == null)
                Interlocked.CompareExchange<ConnectionPoints>(ref _instance, new ConnectionPoints(), null);

            return _instance;
        }

        public ConnectionPoint GetConnectionPointForUniqueId(int uniqueId)
        {
            return ConnectionPointLookupByUniqueId[uniqueId];
        }

        public void CleanupConnectionPoints()
        {
            lock (_lockObject)
            {
                var all = ConnectionPointLookupByUniqueId.AllItems;

                for (int n = all.Count - 1; n >= 0; n--)
                {
                    var c = all[n];
                    if (Globals.Server.FindConnectionForUniqueId(c.UniqueId) == null)
                    {
                        ConnectionPointLookupByUniqueId.Remove(c.UniqueId);
                    }
                }
            }
        }

        public ConnectionPoint SetConnectionPoint(int uniqueId, long gameThingId)
        {
            ConnectionPoint c = ConnectionPointLookupByUniqueId[uniqueId];
            if (c == null)
            {
                lock (_lockObject)
                {
                    c = ConnectionPointLookupByUniqueId[uniqueId];
                    if (c == null)
                    {
                        c = new ConnectionPoint(uniqueId, gameThingId);
                        ConnectionPointLookupByUniqueId.AddOrSet(uniqueId, c);
                    }
                }
            }

            ICommunicationsBase comm = Globals.Server.FindConnectionForUniqueId(uniqueId);

            if (comm != null)
                comm.AssociatedID = gameThingId;

            c.CommandRecipientID = gameThingId;

            CleanupConnectionPoints();
            return c;
        }
    }

    [Serializable]
    [DataContract]
    public class ConnectionPoint
    {
        public int UniqueId { get; set; }
        public ConnectionPoint() { }

        public ConnectionPoint(int uniqueId, long id)
        {
            UniqueId = uniqueId;
            SubscribeToNotificationPublisher(id, true);
            SetIssueCommandsTo(id);
        }

        [field: NonSerialized]
        public event EventHandler NewNotificationPublished;

        public long PublisherID { get; set; }
        public long CommandRecipientID { get; set; }

        protected ThreadSafeQueue<INotificationMessage> _notifications;

        public ThreadSafeQueue<INotificationMessage> Notifications
        {
            get
            {
                if (_notifications == null)
                    Interlocked.CompareExchange<ThreadSafeQueue<INotificationMessage>>(ref _notifications, new ThreadSafeQueue<INotificationMessage>(), null);

                return _notifications;
            }
            set
            {
                _notifications = value;
            }
        }

        public void SubscribeToNotificationPublisher(long publisher, bool subscribe)
        {
            IRaisesNotifications publisherObject = Everything.Find<IGameThing>(publisher) as IRaisesNotifications;
            SubscribeToNotificationPublisher(publisherObject, subscribe);
        }

        public void SetIssueCommandsTo(long recipient)
        {
            CommandRecipientID = recipient;
        }

        public void SubscribeToNotificationPublisher(IRaisesNotifications publisher, bool subscribe)
        {
            if (subscribe && PublisherID == 0)
            {
                PublisherID = publisher.AssociatedID;
                publisher.NotificationRaised += new EventHandler<ObjectEventArgs<INotificationMessage>>(publisher_NotificationRaised);
            }
            else
            {
                PublisherID = 0;
                publisher.NotificationRaised -= publisher_NotificationRaised;
            }
        }

        void publisher_NotificationRaised(object sender, ObjectEventArgs<INotificationMessage> e)
        {
            if (Globals.Server != null)
            {
                ICommunicationsBase conn = Globals.Server.FindConnectionForUniqueId(UniqueId);
                if (conn != null)
                {
                    // HEREHERE - may be a mistake to send in this thread?
                    if (e.obj.NotificationObject is ICanCustomSerialize)
                    {
                        ICanCustomSerialize notifyObj = e.obj.NotificationObject as ICanCustomSerialize;
                        conn.SendMessage(notifyObj, notifyObj.TransmissionType);
                    }
                    else
                    {
                        string s = e.obj.NotificationObject.ToString();
                        NotificationMessageText msg = new NotificationMessageText() { Text = s };
                        conn.SendMessage(msg, (int)TransmissionEnum.NotificationMessageText);
                    }
                }
            }

            if (Notifications.Enqueue(e.obj) > 300)
            {
                INotificationMessage[] messages = new INotificationMessage[200];
                Notifications.DequeueMultiple(messages, 200);
            }

            OnNewNotificationPublished();
        }

        public NotificationPickup PickupPendingNotifications()
        {
            if (Notifications.Count == 0)
                return null;

            NotificationPickup pickup = new NotificationPickup() { Notifications = new List<INotificationMessage>() };
            INotificationMessage o;
            while (Notifications.Dequeue(out o))
            {
                pickup.Notifications.Add(o);

                if (pickup.Notifications.Count > 100)
                    pickup.Notifications.RemoveRange(0, 80);
            }

            return pickup;
        }

        public void OnNewNotificationPublished()
        {
            if (NewNotificationPublished != null)
                NewNotificationPublished(this, new EventArgs());
        }

        public object SendCommandToGameThing(CommandToPerform command)
        {
            ICanReceiveCommands inGameCommandRecipient = Everything.Find<IGameThing>(CommandRecipientID) as ICanReceiveCommands;
            return inGameCommandRecipient.HandleCommandReceived(command);
        }
    }
}